﻿// 2022/4/1: 首个版本
// 2022/10/12: SetConfig过滤相同ID设备
// 2022/10/24: 新增CANFD_2d5M
// 2023/2/20: 增加BusChannelConfig.RemoteType
// 2023/4/10: 增加Load/Save方法
// 2023/4/23: 新增CANFD_5M

using System;
using System.Collections.Generic;
using System.Xml;
using ASEva;
using ASEva.Utility;
using SO = System.Reflection.ObfuscationAttribute;

namespace CommonSource
{
    enum BusBaudRate
    {
        Unknown = 0,

        CAN_5k = 1,
        CAN_10k = 2,
        CAN_20k = 3,
        CAN_33k = 18,
        CAN_40k = 4,
        CAN_50k = 5,
        CAN_62k = 6,
        CAN_80k = 7,
        CAN_83k = 8,
        CAN_100k = 9,
        CAN_125k = 10,
        CAN_200k = 11,
        CAN_250k = 12,
        CAN_400k = 13,
        CAN_500k = 14, // default
        CAN_666k = 15,
        CAN_800k = 16,
        CAN_1M = 17,

        CANFD_500k = 101,
        CANFD_1M = 102,
        CANFD_2M = 103, // default
        CANFD_4M = 104,
        CANFD_8M = 105,
        CANFD_6M = 106,
        CANFD_2d5M = 107,
        CANFD_5M = 108,

        LIN_1k = 201,
        LIN_9d6k = 202, // default
        LIN_16d5k = 203,
        LIN_19d2k = 204,
        LIN_20k = 205,

        FR_2d5M = 301,
        FR_5M = 302,
        FR_10M = 303, // default
    };

    class BusDeviceConfig
    {
        public bool Enabled { get; set; }
        public BusDeviceID DeviceID { get; set; }
        public BusChannelType Type { get; set; }
        public BusBaudRate Rate { get; set; }
        public BusBaudRate SubRate { get; set; }
        public int MapChannel { get; set; }
    }
    
    class BusChannelConfig
    {
        public List<BusProtocolFileID> ProtocolFiles { get; set; }
        public BusChannelType RemoteType { get; set; }
    }

    [SO]
    enum BusSignalBindingType
    {
        Normal,
        Counter,
        Validator,
    }

    class BusSignalBinding
    {
        public String SignalID { get; set; }
        public BusSignalBindingType BindingType { get; set; }
        public String SourceSignal { get; set; }
        public double SourceSignalScale { get; set; }
        public double DefaultValue { get; set; }

        public BusSignalBinding()
        {
            BindingType = BusSignalBindingType.Normal;
            SourceSignalScale = 1;
        }
    }

    class ValidatorXORParams
    {
        // none
    }

    class ValidatorCRC8Params
    {
        public byte Origin { get; set; }
        public byte Poly { get; set; }
        public bool Inversion { get; set; }
    }

    class BusMessageBinding
    {
        public List<BusSignalBinding> Bindings { get; set; }
        public object ValidatorParams { get; set; }

        public BusMessageBinding()
        {
            Bindings = new List<BusSignalBinding>();
        }
    }

    class BusConfig
    {
        public BusConfig()
        {
            SetConfig(Xml.Create("root").InnerXml);
        }

        public static BusConfig Load(object caller)
        {
            var config = new BusConfig();
            var configString = Agency.GetModuleConfig(caller, "bus.system");
            config.SetConfig(configString);
            return config;
        }

        public void Save(object caller)
        {
            Agency.SetModuleConfig(caller, "bus.system", GetConfig());
        }

        public void SetConfig(String config)
        {
            if (String.IsNullOrEmpty(config)) return;

            var xml = new XmlDocument();
            xml.LoadXml(config);
            var rootNode = xml.DocumentElement;

            var p = new AttributeParser(rootNode);

            var deviceNodes = rootNode.GetElementsByTagName("device");
            Devices = new List<BusDeviceConfig>();
            foreach (XmlElement node in deviceNodes)
            {
                var cp = new AttributeParser(node);

                var dev = new BusDeviceConfig();
                dev.Enabled = cp.ParseBool("enabled", "yes", false);
                dev.MapChannel = cp.ParseInt("map_channel", 1);
                dev.Type = (BusChannelType)cp.ParseInt("channel_type", (int)BusChannelType.None);
                dev.Rate = (BusBaudRate)cp.ParseInt("bit_rate", 0);
                if (dev.Rate == BusBaudRate.Unknown)
                {
                    if (dev.Type == BusChannelType.Can) dev.Rate = BusBaudRate.CAN_500k;
                    else if (dev.Type == BusChannelType.CanFD) dev.Rate = BusBaudRate.CANFD_2M;
                    else if (dev.Type == BusChannelType.Lin) dev.Rate = BusBaudRate.LIN_9d6k;
                    else if (dev.Type == BusChannelType.Flexray) dev.Rate = BusBaudRate.FR_10M;
                    else dev.Rate = BusBaudRate.CAN_500k;
                }
                dev.SubRate = (BusBaudRate)cp.ParseInt("bit_rate_sub", 0);
                if (dev.SubRate == BusBaudRate.Unknown && dev.Type == BusChannelType.CanFD)
                {
                    dev.SubRate = BusBaudRate.CAN_500k;
                }

                var id = new BusDeviceID();
                id.Type = cp.ParseString("device_type", null);
                int dummy = 0;
                if (id.Type == null || Int32.TryParse(id.Type, out dummy)) continue;
                id.Serial = cp.ParseLong("device_serial", 0);
                id.Index = cp.ParseInt("device_channel", 0);
                dev.DeviceID = id;

                if (Devices.Exists(d => d.DeviceID.Equals(id))) continue;

                Devices.Add(dev);
            }

            Channels = new BusChannelConfig[16];
             for (int i = 0; i < 16; i++)
            {
                Channels[i] = new BusChannelConfig();
                Channels[i].ProtocolFiles = new List<BusProtocolFileID>();
                Channels[i].RemoteType = BusChannelType.None;

                var chNodes = rootNode.GetElementsByTagName("ch" + i);
                if (chNodes.Count == 0) continue;

                var chNode = chNodes[0] as XmlElement;
                var fileNodes = chNode.GetElementsByTagName("protocol_file");

                foreach (XmlElement fileNode in fileNodes)
                {
                    var cp = new AttributeParser(fileNode);

                    var fileID = new BusProtocolFileID();
                    fileID.FileName = cp.ParseString("id", null);
                    if (fileID == null) continue;
                    fileID.MD5 = cp.ParseString("md5", null);
                    if (fileID.MD5 == null) continue;

                    Channels[i].ProtocolFiles.Add(fileID);
                }

                var rcp = new AttributeParser(chNode);
                Channels[i].RemoteType = (BusChannelType)rcp.ParseInt("remote_type", (int)BusChannelType.None);
            }

            var bdNodes = rootNode.GetElementsByTagName("bd");
            Bindings = new Dictionary<string, BusMessageBinding>();
            foreach (XmlElement bdNode in bdNodes)
            {
                var cp = new AttributeParser(bdNode);
                var message = cp.ParseString("message", null);
                if (message == null) continue;

                var sgNodes = bdNode.GetElementsByTagName("sg");
                var signals = new List<BusSignalBinding>();
                foreach (XmlElement sgNode in sgNodes)
                {
                    var scp = new AttributeParser(sgNode);
                    var newSignal = new BusSignalBinding()
                        {
                            SignalID = scp.ParseSignalID("signal"),
                            BindingType = (BusSignalBindingType)scp.ParseEnum("type", typeof(BusSignalBindingType), BusSignalBindingType.Normal),
                            SourceSignal = scp.ParseSignalID("source"),
                            SourceSignalScale = scp.ParseDouble("scale", 1),
                            DefaultValue = scp.ParseDouble("default", 0),
                        };
                    if (newSignal.SignalID != null) signals.Add(newSignal);
                }
                if (signals.Count == 0) continue;

                var vdNodes = bdNode.GetElementsByTagName("vd");
                object validatorParams = null;
                if (vdNodes.Count > 0)
                {
                    var vcp = new AttributeParser(vdNodes[0] as XmlElement);
                    var type = vcp.ParseString("type", "xor");
                    if (type == "xor")
                    {
                        validatorParams = new ValidatorXORParams();
                    }
                    else if (type == "crc8")
                    {
                        validatorParams = new ValidatorCRC8Params()
                        {
                            Origin = (byte)vcp.ParseInt("origin", 0),
                            Poly = (byte)vcp.ParseInt("poly", 0),
                            Inversion = vcp.ParseBool("inversion", "yes", false),
                        };
                    }
                }

                var binding = new BusMessageBinding()
                {
                    Bindings = signals,
                    ValidatorParams = validatorParams,
                };
                Bindings[message] = binding;
            }
        }

        public String GetConfig()
        {
            var xml = Xml.Create("root");
            var rootNode = xml.DocumentElement;

            var w = new AttributeWriter(rootNode);

            foreach (var dev in Devices)
            {
                var cw = new AttributeWriter(xml, rootNode.AppendChild(xml.CreateElement("device")) as XmlElement);
                cw.WriteBool("enabled", dev.Enabled, "yes", "no");
                cw.WriteString("device_type", dev.DeviceID.Type);
                cw.WriteLong("device_serial", dev.DeviceID.Serial);
                cw.WriteInt("device_channel", dev.DeviceID.Index);
                cw.WriteInt("map_channel", dev.MapChannel);
                cw.WriteInt("bit_rate", (int)dev.Rate);
                cw.WriteInt("bit_rate_sub", (int)dev.SubRate);
                cw.WriteInt("channel_type", (int)dev.Type);
            }

            for (int i = 0; i < 16; i++)
            {
                var chNode = rootNode.AppendChild(xml.CreateElement("ch" + i)) as XmlElement;
                foreach (var file in Channels[i].ProtocolFiles)
                {
                    var cw = new AttributeWriter(xml, chNode.AppendChild(xml.CreateElement("protocol_file")) as XmlElement);
                    cw.WriteString("id", file.FileName);
                    cw.WriteString("md5", file.MD5);
                }
                var rcw = new AttributeWriter(chNode);
                rcw.WriteInt("remote_type", (int)Channels[i].RemoteType);
            }

            foreach (var binding in Bindings)
            {
                var bdNode = rootNode.AppendChild(xml.CreateElement("bd")) as XmlElement;
                bdNode.Attributes.Append(xml.CreateAttribute("message")).Value = binding.Key;

                foreach (var signal in binding.Value.Bindings)
                {
                    var cw = new AttributeWriter(xml, bdNode.AppendChild(xml.CreateElement("sg")) as XmlElement);
                    cw.WriteString("signal", signal.SignalID);
                    cw.WriteString("type", signal.BindingType.ToString());
                    cw.WriteString("source", signal.SourceSignal);
                    cw.WriteDouble("scale", signal.SourceSignalScale);
                    cw.WriteDouble("default", signal.DefaultValue);
                }

                var validatorParams = binding.Value.ValidatorParams;
                if (validatorParams != null)
                {
                    if (validatorParams is ValidatorXORParams)
                    {
                        var vcw = new AttributeWriter(xml, bdNode.AppendChild(xml.CreateElement("vd")) as XmlElement);
                        vcw.WriteString("type", "xor");
                    }
                    else if (validatorParams is ValidatorCRC8Params)
                    {
                        var crc8Params = validatorParams as ValidatorCRC8Params;
                        var vcw = new AttributeWriter(xml, bdNode.AppendChild(xml.CreateElement("vd")) as XmlElement);
                        vcw.WriteString("type", "crc8");
                        vcw.WriteInt("origin", crc8Params.Origin);
                        vcw.WriteInt("poly", crc8Params.Poly);
                        vcw.WriteBool("inversion", crc8Params.Inversion, "yes", "no");
                    }
                }
            }

            return xml.InnerXml;
        }

        public ConfigStatus GetConfigStatus()
        {
            var appMode = Agency.GetAppMode();

            bool hasError = false, hasOK = false;
            if (appMode == ApplicationMode.Online)
            {
                var devices = Agency.GetBusDevices();
                if (devices == null) return ConfigStatus.Disabled;

                foreach (var dev in Devices)
                {
                    if (dev.DeviceID.Type.Length == 0) continue;
                    if (!dev.Enabled) continue;
                    if (!IsDeviceChannelOK(dev.DeviceID)) hasError = true;
                    if (devices.ContainsKey(dev.DeviceID)) hasOK = true;
                    else hasError = true;
                }
            }
            else if (appMode == ApplicationMode.Remote)
            {
                foreach (var ch in Channels)
                {
                    if (ch.RemoteType != BusChannelType.None) hasOK = true;
                }
            }
            else
            {
                hasOK = true;
            }

            foreach (var ch in Channels)
            {
                foreach (var file in ch.ProtocolFiles)
                {
                    if (Agency.GetBusProtocolFileState(file) != BusProtocolFileState.OK) hasError = true;
                    else hasOK = true;
                }
            }

            if (appMode == ApplicationMode.Online)
            {
                foreach (var binding in Bindings)
                {
                    if (!IsBindingOK(binding.Key, binding.Value.Bindings)) hasError = true;
                    else hasOK = true;
                }
            }

            if (hasOK)
            {
                if (hasError) return ConfigStatus.EnabledWithWarning;
                else return ConfigStatus.Enabled;
            }
            else
            {
                if (hasError) return ConfigStatus.EnabledWithError;
                else return ConfigStatus.Disabled;
            }
        }

        public void DisableErrorPart()
        {
            var devices = Agency.GetBusDevices();
            foreach (var dev in Devices)
            {
                if (!IsDeviceChannelOK(dev.DeviceID)) dev.Enabled = false;
                if (devices != null && !devices.ContainsKey(dev.DeviceID)) dev.Enabled = false;
            }

            foreach (var ch in Channels)
            {
                var listToRemove = new List<BusProtocolFileID>();
                foreach (var file in ch.ProtocolFiles)
                {
                    if (Agency.GetBusProtocolFileState(file) != BusProtocolFileState.OK) listToRemove.Add(file);
                }
                foreach (var file in listToRemove)
                {
                    ch.ProtocolFiles.Remove(file);
                }
            }

            var toRemove = new List<String>();
            foreach (var bd in Bindings)
            {
                if (!IsBindingOK(bd.Key, bd.Value.Bindings)) toRemove.Add(bd.Key);
            }
            foreach (var bdKey in toRemove)
            {
                Bindings.Remove(bdKey);
            }
        }
        public void DisableAll()
        {
            foreach (var dev in Devices)
            {
                dev.Enabled = false;
            }
            foreach (var ch in Channels)
            {
                ch.ProtocolFiles.Clear();
                ch.RemoteType = BusChannelType.None;
            }
            Bindings.Clear();
        }

        public bool IsDeviceChannelOK(BusDeviceID config)
        {
            var flags = new bool[16];
            foreach (var c in Devices)
            {
                if (c.MapChannel <= 0 || c.MapChannel > 16) continue;
                if (!c.Enabled) continue;
                if (c.DeviceID.Equals(config))
                {
                    return !flags[c.MapChannel - 1];
                }
                else
                {
                    flags[c.MapChannel - 1] = true;
                }
            }
            return false;
        }

        public static bool IsBindingOK(String messageID, List<BusSignalBinding> signals)
        {
            if (!Agency.IsMessageValid(messageID, false)) return false;
            foreach (var signal in signals)
            {
                if (!Agency.IsSignalValid(signal.SignalID, false)) return false;
                if (!Agency.IsSignalValid(signal.SourceSignal, true)) return false;
            }
            return true;
        }

        public List<BusDeviceConfig> Devices { get; set; }
        public BusChannelConfig[] Channels { get; set; } // Length is 16
        public Dictionary<String, BusMessageBinding> Bindings { get; set; }
    }
}
